/*
  Copyright (C) 2011, Kyungjoo Kim
  
  This file is part of LINAL (LINear ALgebra)
  
  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
  
  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  Also add information on how to contact you by electronic and paper mail.
  
  
  Kyungjoo Kim
  iamkyungjoo@gmail.com
*/

#ifndef LINAL_DEVICE_OBJ_HXX
#define LINAL_DEVICE_OBJ_HXX

namespace linal    {

  typedef struct FLA_Obj_view FLA_Obj;    
  typedef class  Matrix_*     Matrix;
  typedef class  Flat_*       Flat;
  typedef class  Device_Obj_* Device_Obj;


  /*!
    Device_Obj_ is inherited from Flat_ Object and it define the interfaces
    for the device buffer controls. The specific implementation should be declared 
    in the device specific languages.
  */
  class Device_Obj_ : public Flat_ {
  private:
  protected:
    Bool_ _buffer_device_created, _dirt, _use;
  public:

    Device_Obj_() : Flat_(),
                    _buffer_device_created(false), 
                    _dirt(true),
                    _use(false) {  }
    
    // this is not created yet
    Device_Obj_(FLA_Obj obj) : Flat_(obj),
                               _buffer_device_created(false), 
                               _dirt(true),
                               _use(false) { }

    Device_Obj_(const Device_Obj_& obj) : Flat_(obj),
                                          _buffer_device_created(false), 
                                          _dirt(true),
                                          _use(false) { }
    
    virtual ~Device_Obj_() { this->free(); }

    // --------------------------------------------------------------     
    // Create / Free
    virtual void create(Int_ type, Int_ m, Int_ n) {
      this->create_without_buffer( type, m, n );
      this->create_device_buffer();
    }
    virtual void free() {
      Matrix_::free();
      
      if (_buffer_device_created)
        free_device_buffer();
    }
    
    virtual void create_device_buffer()=0;
    virtual void free_device_buffer()=0;
    
    virtual void  set_device_buffer(void *buf_ptr)=0;
    virtual void* get_device_buffer()=0;

    // --------------------------------------------------------------     
    // Partition the matrix on the device.
    virtual void extract(Device_Obj_ &A, Int_ m, Int_ n) {
      this->extract( A, m, n, 0, 0 );
    }
    virtual  void extract(Device_Obj_ &A, Int_ m, Int_ n, Int_ offm, Int_ offn) {
      Flat_::extract( A, m, n, offm, offn );
      
      // share the device buffer
      A.set_device_buffer( get_device_buffer() );
      A._buffer_device_created = false;
      
      // share the validity information
      A._dirt = _dirt;
    }

    // --------------------------------------------------------------     
    // Co-processors use their own memory.
    /*!
      Execute cublasGetMatrix ( memory moves from GPU to CPU )
      \param obj [in] FLA_Obj will read GPU buffer
    */
    virtual void get_matrix_to(FLA_Obj obj)=0;
    
    /*!
      Execute cublasSetMatrix ( memory moves from CPU to GPU )
      \param obj [in] FLA_Obj whose contenst will be written in GPU
    */
    virtual void set_matrix_from(FLA_Obj obj, Int_ &cache_hit)=0;


    // --------------------------------------------------------------     
    // Misc..
    void set_dirt(Bool_ dirt) { _dirt = dirt; }
    Bool_ is_dirty()          { return _dirt; }
    
    void set_use(Bool_ use)   { _use = use; }
    Bool_ is_in_use()         { return _use; }
    
    /*! Compare the buffer on the host from the one in device elementwise.
      \param obj_host  [in] FLA_Obj on the host.
      \param threshold [in] threshold to be considered true. 
    */
    Bool_ compare_buffer(FLA_Obj obj_host, double threshold) {
      FLA_Obj obj_device, norm;
      
      Int_ datatype = FLA_Obj_datatype( obj_host );
      Int_ length   = FLA_Obj_length  ( obj_host );
      Int_ width    = FLA_Obj_width   ( obj_host );
      
      FLA_Obj_create( datatype, length, width, 0, 0, &obj_device );
      FLA_Obj_create( datatype,      1,     1, 0, 0, &norm );
      
      get_matrix_to( obj_device );
      
      FLA_Axpy( FLA_MINUS_ONE, obj_host, obj_device );
      FLA_Norm1( obj_device, norm );
      
      double val;
      switch ( datatype ) {
      case FLA_FLOAT:
      case FLA_COMPLEX:
        val = (double)(*((float *)FLA_Obj_buffer_at_view(norm)));
        break;
      case FLA_DOUBLE:
      case FLA_DOUBLE_COMPLEX:
        val = *((double*)FLA_Obj_buffer_at_view(norm));
        break;
      }
      
      FLA_Obj_free( &norm );
      FLA_Obj_free( &obj_device );
      
      return ( val < threshold );
    }

    // --------------------------------------------------------------     
    // Disp
    virtual std::ostream& content(std::ostream& os) const {
      //this->Disp_::content(os);
      if (this->get_m() && this->get_n()) {
        os << "- Device Obj is ";

        if (this->_dirt) os << "dirty";
        else            os << "clean";
        os << std::endl;
        
        switch (this->get_datatype()) {
        case LINAL_INT:            os << "INT     TYPE\n";  break;
        case LINAL_SINGLE_REAL:   
        case LINAL_DOUBLE_REAL:    os << "REAL    TYPE\n";  break;
        case LINAL_SINGLE_COMPLEX: 
        case LINAL_DOUBLE_COMPLEX: os << "COMPLEX TYPE\n";  break;
        }
        os << "offset(" << this->get_offm() 
           << ", " 
           << this->get_offn() 
           << "), dim(" << this->get_m() 
           << ", " 
           << this->get_n() 
           << ")" << std::endl;

      }
      return os;
    }

  };
}

#endif
